--- title: IceVision Bboxes - Real Data keywords: fastai sidebar: home_sidebar nb_path: "nbs/kfold_bboxes.ipynb" ---
*This is a mashup of IceVision's "Custom Parser" example and their "Getting Started (Object Detection)" notebooks
If on Colab run the following cell, else check the installation instructions
# Warning: This takes a while!
! [ -e /content ] && wget https://raw.githubusercontent.com/airctic/icevision/master/install_colab.sh
! [ -e /content ] && chmod +x install_colab.sh && ./install_colab.sh
! [ -e /content ] && pip install git+git://github.com/airctic/icevision.git --upgrade && kill -9 -1
!pip install espiownage -Uqq
from espiownage.core import *
sysinfo()
dataset_name = 'cleaner' # choose from: cleaner, preclean, spnet, cyclegan, fake
k = 0 # for k-fold cross-validation
model_choice = 0 # IceVision object detector backbone; see below
nk = 5 # number of k-folds (leave as 5)
use_wandb = True # can set to false if no WandB login/tracking is desired
project = 'bbox_kfold' # project name for wandb
from icevision.all import * # this one takes a while
import pandas as pd
from mrspuff.utils import on_colab
from icevision.models.checkpoint import * # as a test to make sure IV is installed properly
if on_colab():
data_dir = untar_data(f'http://hedges.belmont.edu/~shawley/espiownage-{dataset_name}.tgz')
else:
data_dir = Path(f'/home/shawley/datasets/espiownage-{dataset_name}')
print(data_dir)
df = pd.read_csv(data_dir / "bboxes/annotations.csv")
# shuffle rows: if you don't do this next line then my manual/DIY k-folding will go badly
df = df.sample(frac=1).reset_index(drop=True)
df['label'] = 'AN' # all objects as one class: "antinode"
df.head()
template_record = ObjectDetectionRecord()
class BBoxParser(Parser):
def __init__(self, template_record, data_dir):
super().__init__(template_record=template_record)
self.data_dir = data_dir
self.df = pd.read_csv(data_dir / "bboxes/annotations.csv")
self.df['label'] = 'AN' # make them all the same object
# shuffle rows: if you don't do this next line then my manual/DIY k-folding will go badly
self.df = self.df.sample(frac=1).reset_index(drop=True) # shuffle rows
self.class_map = ClassMap(list(self.df['label'].unique()))
def __iter__(self) -> Any:
for o in self.df.itertuples():
yield o
def __len__(self) -> int:
return len(self.df)
def record_id(self, o) -> Hashable:
return o.filename
def parse_fields(self, o, record, is_new):
if is_new:
record.set_filepath(self.data_dir / 'images' / o.filename)
record.set_img_size(ImgSize(width=o.width, height=o.height))
record.detection.set_class_map(self.class_map)
record.detection.add_bboxes([BBox.from_xyxy(o.xmin, o.ymin, o.xmax, o.ymax)])
record.detection.add_labels([o.label])
parser = BBoxParser(template_record, data_dir)
kfold = True
if not kfold:
print("\n------\n Random splitting")
parser = BBoxParser(template_record, data_dir)
else:
k = 0 # manual k-folding index -- change this yourself k = 0 to (nk-1)
nk = 5 # number of k-folds
print(f"\n-----\n K-fold splitting: {k+1}/{nk}")
n = len(df)
idmap, indlist = IDMap(list(df['filename'][0:n])), list(range(n))
val_size = int(round(n/nk))
if k < nk-1:
val_list = indlist[k*val_size:(k+1)*val_size]
train_list = indlist[0:k*val_size] + indlist[(k+1)*val_size:n]
else: # last one might be a bit different
val_list = indlist[k*val_size:]
train_list = indlist[0:-len(val_list)]
val_id_list = list([df['filename'][i] for i in val_list])
train_id_list = list([df['filename'][i] for i in train_list])
presplits = list([train_id_list,val_id_list])
train_records, valid_records = parser.parse(data_splitter=FixedSplitter(presplits))
Quick check: Let's take a look at one record of target data:
show_record(train_records[5], display_label=False, figsize=(14, 10))
# size is set to 384 because EfficientDet requires its inputs to be divisible by 128
image_size = 384
train_tfms = tfms.A.Adapter([*tfms.A.aug_tfms(size=image_size, presize=512), tfms.A.Normalize()])
valid_tfms = tfms.A.Adapter([*tfms.A.resize_and_pad(image_size), tfms.A.Normalize()])
# Datasets
train_ds = Dataset(train_records, train_tfms)
valid_ds = Dataset(valid_records, valid_tfms)
samples = [train_ds[0] for _ in range(3)]
show_samples(samples, ncols=3)
## this has been moved up to "Run Parameters" look up^^ #
selection = model_choice
extra_args = {}
if selection == 0:
model_type = models.mmdet.retinanet
backbone = model_type.backbones.resnet50_fpn_1x
model_name='mmdet.retinanet'
backbone_name = 'resnet50_fpn_1x'
elif selection == 1:
# The Retinanet model is also implemented in the torchvision library
model_type = models.torchvision.retinanet
backbone = model_type.backbones.resnet50_fpn
model_name='torchvision.retinanet'
backbone_name = 'resnet50_fpn'
elif selection == 2:
model_type = models.ross.efficientdet
backbone = model_type.backbones.tf_lite0
# The efficientdet model requires an img_size parameter
model_name='ross.efficientdet'
backbone_name = 'tf_lite0'
extra_args['img_size'] = image_size
elif selection == 3:
model_type = models.ultralytics.yolov5
backbone = model_type.backbones.small
model_name='ultralytics.yolov5'
backbone_name = 'small'
# The yolov5 model requires an img_size parameter
extra_args['img_size'] = image_size
model_type, backbone, extra_args
if use_wandb:
!pip install wandb -qqq
import wandb
from fastai.callback.wandb import *
from fastai.callback.tracker import SaveModelCallback
wandb.login()
model = model_type.model(backbone=backbone(pretrained=True), num_classes=len(parser.class_map), **extra_args)
train_dl = model_type.train_dl(train_ds, batch_size=8, num_workers=4, shuffle=True)
valid_dl = model_type.valid_dl(valid_ds, batch_size=8, num_workers=4, shuffle=False)
# show batch - target data -- Ideally these won't all look the same! ;-)
model_type.show_batch(first(valid_dl), ncols=4)
metrics = [COCOMetric(metric_type=COCOMetricType.bbox)]
if use_wandb:
wandb.init(project=project, name=f'k={k},m={model_choice},{dataset_name}')
cbs = [WandbCallback()]
else:
cbs = []
learn = model_type.fastai.learner(dls=[train_dl, valid_dl], model=model, metrics=metrics,
cbs=cbs)
learn.lr_find(end_lr=0.005)
epochs = 11 if kfold else 30 # really 10 or 11 is where things settle down
lr = 1e-4
freeze_epochs=2
print(f"Training for {epochs} epochs, starting with {freeze_epochs} frozen epochs...")
learn.fine_tune(epochs, lr, freeze_epochs=2)
wandb.finish()
model_type.show_results(model, valid_ds, detection_threshold=.5)
checkpoint_path = f'bbox-k{k}-m{model_choice}-{dataset_name}.pth'
save_icevision_checkpoint(model,
model_name=model_name,
backbone_name=backbone_name,
classes = parser.class_map.get_classes(),
img_size=384,
filename=checkpoint_path,
meta={'icevision_version': '0.9.1'})
checkpoint_and_model = model_from_checkpoint(checkpoint_path,
model_name=model_name,
backbone_name=backbone_name,
img_size=384)
model.to('cuda')
device=next(model.parameters()).device
device
infer_ds = valid_ds
infer_dl = model_type.infer_dl(infer_ds, batch_size=4, shuffle=False)
preds = model_type.predict_from_dl(model, infer_dl, keep_images=True)
#Do NOT use: preds = model_type.predict(model, valid_ds, keep_images=True)
show_preds(preds=preds[0:5])
len(train_ds), len(valid_ds), len(preds)
let's try to figure out how to get what we want from these predictions. hmmm
def get_bblist(pred):
my_bblist = []
bblist = pred.pred.detection.bboxes
for i in range(len(bblist)):
my_bblist.append([bblist[i].xmin, bblist[i].ymin, bblist[i].xmax, bblist[i].ymax])
return my_bblist
get_bblist(preds[1])
results = []
for i in range(len(preds)):
if (len(preds[i].pred.detection.scores) == 0): continue # sometimes you get a zero box/prediction. ??
#print(f"i = {i}, file = {str(Path(valid_ds[i].common.filepath).stem)+'.csv'}, bboxes = {get_bblist(preds[i])}, scores={preds[i].pred.detection.scores}\n")
worst_score = np.min(np.array(preds[i].pred.detection.scores))
line_list = [str(Path(valid_ds[i].common.filepath).stem)+'.csv', get_bblist(preds[i]), preds[i].pred.detection.scores, worst_score, i]
results.append(line_list)
# store as pandas dataframe
res_df = pd.DataFrame(results, columns=['filename', 'bblist','scores','worst_score','i'])
res_df = res_df.sort_values('worst_score') # order by worst score as a "top losses" kind of thing
res_df.head() # take a look
if not kfold:
res_df.to_csv('bboxes_top_losses_real.csv', index=False)
else:
res_df.to_csv(f'bboxes_top_losses_real_k{k}.csv', index=False)